package com.zyh.em.evaluate;

import com.zyh.em.entity.EvaluateData;
import com.zyh.em.entity.EvaluateReport;
import com.zyh.em.entity.HistoryData;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 乱序密码的评估：通过余弦相似性完成
 */
public class OrdernessPasswordEvaluate  extends AbstractEvaluate{


    private double threshold;//相似性阈值

    public OrdernessPasswordEvaluate(double threshold) {
        this.threshold = threshold;
    }

    @Override
    public void evaluate(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        //获取待评估的用户输入和历史输入
         String ordernessPassword = evaluateData.getOrdernessPassword();
        Set<String> ordernessPasswords = historyData.getOrdernessPasswords();

        if(ordernessPasswords == null || ordernessPasswords.size() == 0){
            evaluateReport.setRiskFactor("ordernessPassword",false);//如果是第1次则没有风险
        }else {
            boolean isRisk = doEval(ordernessPassword, ordernessPasswords);
            evaluateReport.setRiskFactor("ordernessPassword", isRisk);
        }

        evaluateChain.doEvaluate(evaluateData, historyData, evaluateReport);
    }

    /**
     * 评估处理
     * @param ordernessPassword 评估乱序密码
     这是要给猪身上
     * @param set 历次成功登录的乱序密码
     * @return 是否有风险
     */
    private boolean doEval(String ordernessPassword, Set<String> set) {

        //把所有的密码都转换成向量
        //在Java中向量就是int数组
        /**
         * 把之前使用过的所有字符以及这一次评估中使用的所有字符，剔除掉重复的--list
         * 乱序密码要转换成向量：
         * list中的每一个字符，在乱序密码中出现的次数，形成int数组，这个数组就是乱序密码对应的向量
         */


        Set<Character> s = new HashSet<>();
        //把之前成功登录的所有使用的字符，放入到s里面
        for (String str : set) {
            for (char c : str.toCharArray()) {
                s.add(c);
            }
        }
        for (char c : ordernessPassword.toCharArray()) {
            s.add(c);
        }

        //词条库
        List<Character> list = s.stream().sorted().collect(Collectors.toList());

        //本次评估密码对应的向量
        int[] vector = calcVector(ordernessPassword, list);

        //历次成功登录的密码对应的向量
        List<int[]> l = set
                .stream()
                .map(password -> calcVector(password, list))
                .collect(Collectors.toList());

        //计算vector和l中的每一个元素对应的余弦相似性
        List<Double> allSimilarity = l
                .stream()
                .map(e -> calcCosineSimilarity(e, vector))
                .collect(Collectors.toList());

        List<Double> similarity = allSimilarity
                .stream()
                .filter(e -> e > threshold)//大于规定值的数据保留下来
                .collect(Collectors.toList());

        
        return similarity.size()==0;
    }


    /**
     * 就ordernessPassword的向量
     * @param ordernessPassword 乱序密码
     * @param list 词条库/词袋库，所有用过的字符
     * @return ordernessPassword这个字符串对应的向量
     */
    private int[] calcVector(String ordernessPassword, List<Character> list){
        int[] vector=new int[list.size()];

        //list中的每一个字符，在ordernessPassword中出现的次数
        Map<Character,Integer> map=new HashMap<>();
        for (Character character : list) {
            for (char c : ordernessPassword.toCharArray()) {
                if(character==c){
                     map.put(character,map.getOrDefault(character,0)+1);
                }
            }
        }

        for (int i = 0; i < list.size(); i++) {
            vector[i]=map.containsKey(list.get(i))?map.get(list.get(i)):0;
        }



        return vector;
    }

    /**
     * 计算两个向量的余弦相似性
     * @param a 向量a
     * @param b 向量b
     * @return 两个向量的余弦相似性
     */
    private double calcCosineSimilarity(int[] a,int[] b){
        double fenZi=0;
        for (int i = 0; i < a.length; i++) {
            fenZi+=a[i]*b[i];
        }

        double sumA=0;
        double sumB=0;

        for (int i = 0; i < a.length; i++) {
            sumA+=Math.pow(a[i],2);
            sumB+=Math.pow(b[i],2);
        }
        double fenMu=Math.sqrt(sumA)*Math.sqrt(sumB);

        return fenZi/fenMu;
    }
}